#include "jtest.h"
#include "filtering_test_data.h"
#include "arr_desc.h"
#include "arm_math.h"           /* FUTs */
#include "ref.h"                /* Reference Functions */
#include "test_templates.h"
#include "filtering_templates.h"
#include "type_abbrev.h"

/*--------------------------------------------------------------------------------*/
/* Header Stuff */
/*--------------------------------------------------------------------------------*/

#define CONV_MAX_INPUT_ELTS  32
#define CONV_MAX_OUTPUT_ELTS (CONV_MAX_INPUT_ELTS * 2)

#define CONV_TEST_VALID_PARTIAL_PARAMS(input_a_len, input_b_len,           \
                                       first_index, num_points)            \
    (((((input_a_len) + (input_b_len) - 1)) >= num_points + first_index )  \
    && (num_points > 0))

/*--------------------------------------------------------------------------------*/
/* Input Interfaces */
/*--------------------------------------------------------------------------------*/
/*
 *  General:
 *  Input interfaces provide inputs to functions inside test templates.  They
 *  ONLY provide the inputs.  The output variables should be hard coded.
 *
 *  The input interfaces must have the following format:
 *
 *  ARM_xxx_INPUT_INTERFACE() or
 *  REF_xxx_INPUT_INTERFACE()
 *
 *  The xxx must be lowercase, and is intended to be the indentifying substring
 *  in the function's name.  Acceptable values are 'sub' or 'add' from the
 *  functions arm_add_q31.
 */

#define CONV_arm_conv_INPUT_INTERFACE(input_a, input_a_len, input_b, input_b_len) \
    PAREN(input_a, input_a_len, input_b, input_b_len, (void*)filtering_output_fut)

#define CONV_ref_conv_INPUT_INTERFACE(input_a, input_a_len, input_b, input_b_len) \
    PAREN(input_a, input_a_len, input_b, input_b_len, (void*)filtering_output_ref)

#define CONV_arm_conv_opt_INPUT_INTERFACE(              \
    input_a, input_a_len,                               \
    input_b, input_b_len)                               \
    PAREN(input_a, input_a_len, input_b, input_b_len,   \
          (void*) filtering_output_fut,                 \
          (void*) filtering_scratch,                    \
          (void*) filtering_scratch2)

#define CONV_ref_conv_opt_INPUT_INTERFACE(              \
    input_a, input_a_len,                               \
    input_b, input_b_len)                               \
    PAREN(input_a, input_a_len, input_b, input_b_len,   \
          (void*) filtering_output_ref,                 \
          (void*) filtering_scratch,                    \
          (void*) filtering_scratch2)

#define CONV_arm_conv_fast_INPUT_INTERFACE(input_a, input_a_len,        \
                                           input_b, input_b_len)        \
    PAREN(input_a, input_a_len, input_b, input_b_len, (void*)filtering_output_fut)

#define CONV_ref_conv_fast_INPUT_INTERFACE(input_a, input_a_len,        \
                                           input_b, input_b_len)        \
    PAREN(input_a, input_a_len, input_b, input_b_len, (void*)filtering_output_ref)

#define CONV_arm_conv_fast_opt_INPUT_INTERFACE(         \
    input_a, input_a_len,                               \
    input_b, input_b_len)                               \
    PAREN(input_a, input_a_len, input_b, input_b_len,   \
          (void*) filtering_output_fut,                 \
          (void*) filtering_scratch,                    \
          (void*) filtering_scratch2)

#define CONV_ref_conv_fast_opt_INPUT_INTERFACE(         \
    input_a, input_a_len,                               \
    input_b, input_b_len)                               \
    PAREN(input_a, input_a_len, input_b, input_b_len,   \
          (void*) filtering_output_ref,                 \
          (void*) filtering_scratch,                    \
          (void*) filtering_scratch2)

#define CONV_arm_conv_partial_INPUT_INTERFACE(input_a, input_a_len,     \
                                              input_b, input_b_len,     \
                                              first_index, num_points)  \
    PAREN(input_a, input_a_len, input_b, input_b_len,                   \
          (void*)filtering_output_fut, first_index, num_points)

#define CONV_ref_conv_partial_INPUT_INTERFACE(input_a, input_a_len,     \
                                              input_b, input_b_len,     \
                                              first_index, num_points)  \
    PAREN(input_a, input_a_len, input_b, input_b_len,                   \
          (void*)filtering_output_ref, first_index, num_points)

#define CONV_arm_conv_partial_fast_INPUT_INTERFACE(input_a, input_a_len,   \
                                              input_b, input_b_len,        \
                                              first_index, num_points)     \
    PAREN(input_a, input_a_len, input_b, input_b_len,                      \
          (void*)filtering_output_fut, first_index, num_points)

#define CONV_ref_conv_partial_fast_INPUT_INTERFACE(input_a, input_a_len,   \
                                              input_b, input_b_len,        \
                                              first_index, num_points)     \
    PAREN(input_a, input_a_len, input_b, input_b_len,                      \
          (void*)filtering_output_ref, first_index, num_points)

#define CONV_arm_conv_partial_opt_INPUT_INTERFACE(input_a, input_a_len, \
                                              input_b, input_b_len,     \
                                              first_index, num_points)  \
    PAREN(input_a, input_a_len, input_b, input_b_len,                   \
          (void*)filtering_output_fut, first_index, num_points,         \
          (void*) filtering_scratch,                                    \
          (void*) filtering_scratch2)

#define CONV_ref_conv_partial_opt_INPUT_INTERFACE(input_a, input_a_len, \
                                              input_b, input_b_len,     \
                                              first_index, num_points)  \
    PAREN(input_a, input_a_len, input_b, input_b_len,                   \
          (void*)filtering_output_ref, first_index, num_points,         \
          (void*) filtering_scratch,                                    \
          (void*) filtering_scratch2)

#define CONV_arm_conv_partial_fast_opt_INPUT_INTERFACE(input_a, input_a_len,  \
                                              input_b, input_b_len,           \
                                              first_index, num_points)        \
    PAREN(input_a, input_a_len, input_b, input_b_len,                         \
          (void*)filtering_output_fut, first_index, num_points,               \
          (void*) filtering_scratch,                                          \
          (void*) filtering_scratch2)

#define CONV_ref_conv_partial_fast_opt_INPUT_INTERFACE(input_a, input_a_len,  \
                                              input_b, input_b_len,           \
                                              first_index, num_points)        \
    PAREN(input_a, input_a_len, input_b, input_b_len,                         \
          (void*)filtering_output_ref, first_index, num_points,               \
          (void*) filtering_scratch,                                          \
          (void*) filtering_scratch2)

/*--------------------------------------------------------------------------------*/
/* Convolution Inputs */
/*--------------------------------------------------------------------------------*/

/* The following symbols alias the filtering_q31_inputs array:
 *
 * - filtering_q15_inputs
 * - filtering_q7_inputs
 *
 * The aliasing conflicts with the instantiation of #ARR_DESC_t structs.
 *
 * These macro-level aliases allow the #CONV_DEFINE_RAND_INPUT_ARR_DESCS() macro
 * to correctly select the filtering_q31_input or filtering_f32_input array,
 * within a template, by type_suffix.
 *
 */
#define CONV_f32_INPUTS filtering_f32_inputs
#define CONV_q31_INPUTS filtering_q31_inputs
#define CONV_q15_INPUTS filtering_q31_inputs
#define CONV_q7_INPUTS  filtering_q31_inputs

/**
 *  Defines #ARR_DESC_t objects that wrap existing, type-specific, common
 *  inputs.
 */
#define CONV_DEFINE_RAND_INPUT_ARR_DESCS(type_suffix)           \
    ARR_DESC_DEFINE_USING_ARR(                                  \
        TYPE_FROM_ABBREV(type_suffix),                          \
        conv_input_rand1_##type_suffix,                         \
        CONV_##type_suffix##_INPUTS,                            \
        0,                                                      \
        CONV_MAX_INPUT_ELTS);                                   \
                                                                \
    ARR_DESC_DEFINE_USING_ARR(                                  \
        TYPE_FROM_ABBREV(type_suffix),                          \
        conv_input_rand2_##type_suffix,                         \
        CONV_##type_suffix##_INPUTS,                            \
        1,                                                      \
        CONV_MAX_INPUT_ELTS)  /* Note the lacking semicolon */

CONV_DEFINE_RAND_INPUT_ARR_DESCS(f32);
CONV_DEFINE_RAND_INPUT_ARR_DESCS(q31);
CONV_DEFINE_RAND_INPUT_ARR_DESCS(q15);
CONV_DEFINE_RAND_INPUT_ARR_DESCS(q7);
ARR_DESC_DEFINE(float32_t, conv_input_zeros, CONV_MAX_INPUT_ELTS, CURLY(0));

/**
 *  Define Input #ARR_DESC_t arrays by type suffix.
 *
 *  Taking inputs in parallel from the 'a' and 'b' arrays yields the following
 *  test cases (star is convolution):
 *
 *  - zero_array   * zero_array
 *  - zero_array   * random_array
 *  - random_array * zero_array
 *  - random_array * different_random_arary
 */
#define CONV_DEFINE_ALL_INPUTS(type_suffix)                 \
    ARR_DESC_DEFINE(ARR_DESC_t *,                           \
                    conv_##type_suffix##_a_inputs,          \
                    4,                                      \
                    CURLY(                                  \
                        &conv_input_zeros,                  \
                        &conv_input_zeros,                  \
                        &conv_input_rand1_##type_suffix,    \
                        &conv_input_rand1_##type_suffix     \
                        ));                                 \
    ARR_DESC_DEFINE(ARR_DESC_t *,                           \
                    conv_##type_suffix##_b_inputs,          \
                    4,                                      \
                    CURLY(                                  \
                        &conv_input_zeros,                  \
                        &conv_input_rand1_##type_suffix,    \
                        &conv_input_zeros,                  \
                        &conv_input_rand2_##type_suffix     \
                        )) /* Note the lacking semicolon */

CONV_DEFINE_ALL_INPUTS(f32);
CONV_DEFINE_ALL_INPUTS(q31);
CONV_DEFINE_ALL_INPUTS(q15);
CONV_DEFINE_ALL_INPUTS(q7);

/*--------------------------------------------------------------------------------*/
/* Convolution Lengths */
/*--------------------------------------------------------------------------------*/

/*
 *  The conv_lens_a and conv_lens_b #ARR_DESC_t objects are accessed in parallel
 *  to provide convolution-length pairs. Taken in parallel they provide the
 *  following cases:
 *
 *  - 1 * 1    : Shortest convolution possible.
 *  - 1 * 2    : Short convolution   , one side is degenerate .
 *  - 17 * 1   : Medium convolution  , one side is degenerate .
 *  - 15 * MAX : Longest convolution , one side is degenerate .
 *  MAX * MAX  : Longest convolution.
 */
ARR_DESC_DEFINE(uint32_t,
                conv_lens_a,
                5,
                CURLY(
                    1,
                    1,
                    17,
                    15,
                    CONV_MAX_INPUT_ELTS
                    ));

ARR_DESC_DEFINE(uint32_t,
                conv_lens_b,
                5,
                CURLY(
                    1,
                    2,
                    1,
                    CONV_MAX_INPUT_ELTS,
                    CONV_MAX_INPUT_ELTS
                    ));

/*--------------------------------------------------------------------------------*/
/* Partial Indexing */
/*--------------------------------------------------------------------------------*/

ARR_DESC_DEFINE(uint32_t,
                first_index_arr_desc,
                4,
                CURLY(
                    0,
                    1,
                    CONV_MAX_INPUT_ELTS / 2,
                    CONV_MAX_INPUT_ELTS
                    ));

ARR_DESC_DEFINE(uint32_t,
                num_points_arr_desc,
                3,
                CURLY(
                    1,
                    CONV_MAX_OUTPUT_ELTS / 2,
                    CONV_MAX_OUTPUT_ELTS
                    ));

/*--------------------------------------------------------------------------------*/
/* Convolution Tests */
/*--------------------------------------------------------------------------------*/

#define CONV_TEST_TEMPLATE(fut, fut_arg_interface,                            \
                           ref, ref_arg_interface,                            \
                           suffix, output_type)                               \
    JTEST_DEFINE_TEST(fut##_tests, fut)                                       \
    {                                                                         \
        TEMPLATE_DO_ARR_DESC(                                                 \
            input_idx, ARR_DESC_t *, input_ptr, conv_##suffix##_a_inputs      \
            ,                                                                 \
            void * input_a_ptr = input_ptr->data_ptr;                         \
            void * input_b_ptr = ARR_DESC_ELT(                                \
                ARR_DESC_t *, input_idx,                                      \
                &(conv_##suffix##_b_inputs))->data_ptr;                       \
                                                                              \
            TEMPLATE_DO_ARR_DESC(                                             \
                conv_len_idx, uint32_t, conv_len_a, conv_lens_a               \
                ,                                                             \
                uint32_t conv_len_b = ARR_DESC_ELT(                           \
                    uint32_t, conv_len_idx, &(conv_lens_b));                  \
                                                                              \
                JTEST_DUMP_STRF("Input A Length: %d\n"                        \
                                "Input B Length: %d\n",                       \
                                (int)conv_len_a,                              \
                                (int)conv_len_b);                             \
                                                                              \
                TEST_CALL_FUT_AND_REF(                                        \
                    fut, fut_arg_interface(                                   \
                        input_a_ptr, conv_len_a, input_b_ptr, conv_len_b),    \
                    ref, ref_arg_interface(                                   \
                        input_a_ptr, conv_len_a, input_b_ptr, conv_len_b));   \
                                                                              \
                FILTERING_SNR_COMPARE_INTERFACE(                              \
                    conv_len_a + conv_len_b - 1,                              \
                    output_type)));                                           \
                                                                              \
        return JTEST_TEST_PASSED;                                             \
    }                                                                         \
                                                                              \

#define CONV_PARTIAL_TEST_TEMPLATE(fut, fut_arg_interface,                    \
                                   ref, ref_arg_interface,                    \
                                   suffix, output_type)                       \
    JTEST_DEFINE_TEST(fut##_tests, fut)                                       \
    {                                                                         \
        TEMPLATE_DO_ARR_DESC(                                                 \
            input_idx, ARR_DESC_t *, input_ptr, conv_##suffix##_a_inputs      \
            ,                                                                 \
            void * input_a_ptr = input_ptr->data_ptr;                         \
            void * input_b_ptr = ARR_DESC_ELT(                                \
                ARR_DESC_t *, input_idx,                                      \
                &(conv_##suffix##_b_inputs))->data_ptr;                       \
            TEMPLATE_DO_ARR_DESC(                                             \
                conv_len_idx, uint32_t, conv_len_a, conv_lens_a               \
                ,                                                             \
                uint32_t conv_len_b = ARR_DESC_ELT(                           \
                    uint32_t, conv_len_idx, &(conv_lens_b));                  \
                                                                              \
                TEMPLATE_DO_ARR_DESC(                                         \
                    first_index_idx, uint32_t, first_index,                   \
                    first_index_arr_desc                                      \
                    ,                                                         \
                    TEMPLATE_DO_ARR_DESC(                                     \
                        num_points_idx, uint32_t, num_points,                 \
                        num_points_arr_desc                                   \
                        ,                                                     \
                        if (CONV_TEST_VALID_PARTIAL_PARAMS(                   \
                                conv_len_a, conv_len_b,                       \
                                first_index, num_points))                     \
                        {                                                     \
                            /* Display test parameter values */               \
                            JTEST_DUMP_STRF("Input A Length: %d\n"            \
                                            "Input B Length: %d\n"            \
                                            "First Sample Index: %d\n"        \
                                            "Number of Output Points: %d\n",  \
                                            (int)conv_len_a,                  \
                                            (int)conv_len_b,                  \
                                            (int)first_index,                 \
                                            (int)num_points);                 \
                                                                              \
                           memset(filtering_output_ref,0,                     \
                                 (2*CONV_MAX_INPUT_ELTS)*sizeof(output_type)); \
                           memset(filtering_output_fut,0,                     \
                                 (2*CONV_MAX_INPUT_ELTS)*sizeof(output_type)); \
                                                                              \
                            TEST_CALL_FUT_AND_REF(                            \
                                fut, fut_arg_interface(                       \
                                    input_a_ptr, conv_len_a,                  \
                                    input_b_ptr, conv_len_b,                  \
                                    first_index, num_points),                 \
                                ref, ref_arg_interface(                       \
                                    input_a_ptr, conv_len_a,                  \
                                    input_b_ptr, conv_len_b,                  \
                                    first_index, num_points));                \
                                                                              \
                            FILTERING_SNR_COMPARE_INTERFACE_OFFSET(           \
                                first_index,                                  \
                                num_points,                                   \
                                output_type);                                 \
                        } else {                                              \
                            /* FUT should return ARM_MATH_ARGUMENT_ERROR*/    \
                            /* if first_index and num_points don't make */    \
                            /* sense*/                                        \
                                                                              \
                            arm_status conv_test_retval;                      \
                            TEST_CALL_FUT(                                    \
                                conv_test_retval = fut,                       \
                                fut_arg_interface(                            \
                                    input_a_ptr, conv_len_a,                  \
                                    input_b_ptr, conv_len_b,                  \
                                    first_index, num_points));                \
                                                                              \
                            if (conv_test_retval != ARM_MATH_ARGUMENT_ERROR) { \
                                JTEST_DUMP_STR("FUT failed to raise error."); \
                                /* return JTEST_TEST_FAILED; */               \
                            }                                                 \
                        }))));                                                \
                                                                              \
        return JTEST_TEST_PASSED;                                             \
    }

#define CONV_DEFINE_TEST(fn_name, suffix, output_type, test_template)   \
    test_template(                                                      \
        arm_##fn_name##_##suffix,                                       \
        CONV_arm_##fn_name##_INPUT_INTERFACE,                           \
        ref_##fn_name##_##suffix,                                       \
        CONV_ref_##fn_name##_INPUT_INTERFACE,                           \
        suffix,                                                         \
        output_type                                                     \
        ) /* Note the lacking semicolon*/

/* Tests on functions without partial outputs */
CONV_DEFINE_TEST(conv          , f32, float32_t, CONV_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv          , q31, q31_t    , CONV_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv          , q15, q15_t    , CONV_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv          , q7 , q7_t     , CONV_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_opt      , q15, q15_t    , CONV_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_opt      , q7 , q7_t     , CONV_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_fast     , q31, q31_t    , CONV_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_fast     , q15, q15_t    , CONV_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_fast_opt , q15, q15_t    , CONV_TEST_TEMPLATE);

/* Tests on functions with partial outputs  */
CONV_DEFINE_TEST(conv_partial          , f32, float32_t, CONV_PARTIAL_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_partial          , q31, q31_t    , CONV_PARTIAL_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_partial          , q15, q15_t    , CONV_PARTIAL_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_partial          , q7 , q7_t     , CONV_PARTIAL_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_partial_fast     , q31, q31_t    , CONV_PARTIAL_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_partial_fast     , q15, q15_t    , CONV_PARTIAL_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_partial_fast_opt , q15, q15_t    , CONV_PARTIAL_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_partial_opt      , q15, q15_t    , CONV_PARTIAL_TEST_TEMPLATE);
CONV_DEFINE_TEST(conv_partial_opt      , q7 , q7_t     , CONV_PARTIAL_TEST_TEMPLATE);

/*--------------------------------------------------------------------------------*/
/* Collect all tests in a group. */
/*--------------------------------------------------------------------------------*/

JTEST_DEFINE_GROUP(conv_tests)
{
  /*
    To skip a test, comment it out.
  */
  JTEST_TEST_CALL(arm_conv_f32_tests);
  JTEST_TEST_CALL(arm_conv_q31_tests);
  JTEST_TEST_CALL(arm_conv_q15_tests);
  JTEST_TEST_CALL(arm_conv_q7_tests);

  JTEST_TEST_CALL(arm_conv_opt_q15_tests);
  JTEST_TEST_CALL(arm_conv_opt_q7_tests);

  JTEST_TEST_CALL(arm_conv_fast_q31_tests);
  JTEST_TEST_CALL(arm_conv_fast_q15_tests);

  JTEST_TEST_CALL(arm_conv_fast_opt_q15_tests);

  JTEST_TEST_CALL(arm_conv_partial_f32_tests);
  JTEST_TEST_CALL(arm_conv_partial_q31_tests);
  JTEST_TEST_CALL(arm_conv_partial_q15_tests);
  JTEST_TEST_CALL(arm_conv_partial_q7_tests);

  JTEST_TEST_CALL(arm_conv_partial_fast_q31_tests);
  JTEST_TEST_CALL(arm_conv_partial_fast_q15_tests);

  JTEST_TEST_CALL(arm_conv_partial_fast_opt_q15_tests);

  JTEST_TEST_CALL(arm_conv_partial_opt_q15_tests);
  JTEST_TEST_CALL(arm_conv_partial_opt_q7_tests);
}
